/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.view.scribbles;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Highlighter;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.basics.Observer;
import org.jfonia.constants.ViewConstants;
import org.jfonia.images.ImageConstants;
import org.jfonia.images.ScaledImageIcon;
import org.jfonia.notation.Notation;

/**
 *
 * @author Rik Bauwens
 */
public class ScribbleView extends JTextArea implements DocumentListener, MouseListener, FocusListener
{
        //Scribble
        private Scribble scribble;

        //Custom font
        private final static String FONT_NAME = "BitchCakes.ttf";

        //Colors
        private final Color BACKGROUND_COLOR = new Color(255, 213, 46);
        private final Color BORDER_COLOR = Color.getHSBColor(
                (float) (47.0 / 360), (float) (53.0 / 100), (float) (80.0 / 100));

        //Dimensions
        private final static int PERCENT = 20;
        private final static double FACTOR = (PERCENT / 100.0);
        private final static int PRE_X = (int) (50 * FACTOR);
        private final static int POST_X = (int) (70 * FACTOR);
        private final static int PRE_Y = (int) (47 * FACTOR);
        private final static int POST_Y = (int) (60 * FACTOR);

        //Images
        private final static Image TOP_RIGHT_IMAGE =
                new ScaledImageIcon(ImageConstants.SCRIBBLE_TOP_RIGHT, "").setScale(PERCENT).getImage();
        private final static Image BOTTOM_LEFT_IMAGE =
                new ScaledImageIcon(ImageConstants.SCRIBBLE_BOTTOM_LEFT, "").setScale(PERCENT).getImage();
        private final static Image BOTTOM_RIGHT_IMAGE =
                new ScaledImageIcon(ImageConstants.SCRIBBLE_BOTTOM_RIGHT, "").setScale(PERCENT).getImage();
        private final static Image CLOSE_IMAGE =
                new ScaledImageIcon(ImageConstants.SCRIBBLE_CLOSE, "").setScale(PERCENT / 10).getImage();
        
        //Bounds
        private final static int MIN_WIDTH = BOTTOM_LEFT_IMAGE.getWidth(null) + BOTTOM_RIGHT_IMAGE.getWidth(null);
        private final static int MIN_HEIGHT = TOP_RIGHT_IMAGE.getHeight(null) + BOTTOM_RIGHT_IMAGE.getHeight(null);
        
        //Margins
        private final static int CLOSE_BOTTON_TOP = 4;
        private final static int CLOSE_BOTTON_LEFT = 4;

        private final static int TOP_MARGIN = ViewConstants.SCRIBBLE_TOP_MARGIN + PRE_Y + CLOSE_BOTTON_TOP;
        private final static int LEFT_MARGIN = ViewConstants.SCRIBBLE_LEFT_MARGIN + PRE_X + CLOSE_BOTTON_LEFT;
        private final static int BOTTOM_MARGIN = ViewConstants.SCRIBBLE_BOTTOM_MARGIN + (int) (140 * FACTOR);
        private final static int RIGHT_MARGIN = ViewConstants.SCRIBBLE_RIGHT_MARGIN + (int) (180 * FACTOR);

        //Alpha
        private float alpha = ViewConstants.SCRIBBLE_DEFAULT_ALPHA;
        private Highlighter highlighter;
        private boolean mouseEntered;

        //Initial dimensions
        public final static int INITIAL_WIDTH = MIN_WIDTH;
        public final static int INITIAL_HEIGHT = MIN_HEIGHT;


        public ScribbleView(Scribble scribble)
        {
                loadFont();
                highlighter = getHighlighter();
                mouseEntered = false;
                setOpaque(false);
                setEditable(false);
                setMargin(new Insets(TOP_MARGIN, LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN));
                //setLineWrap(true);
                getDocument().addDocumentListener(this);
                addMouseListener(this);
                addFocusListener(this);
                this.scribble = scribble;
                init();
        }

        public Scribble getScribble()
        {
                return scribble;
        }

        private void init()
        {
                setBounds(scribble.getX(), scribble.getY(), MIN_WIDTH, MIN_HEIGHT);
                setText(scribble.getText());
                scribble.getXNode().addObserver(new Observer()
                {
                        public void onNotify(Object source)
                        {
                                setLocation(((MutableValueNode<Integer>) source).getValue(), getY());
                        }
                });
                scribble.getYNode().addObserver(new Observer()
                {
                        public void onNotify(Object source)
                        {
                                setLocation(getX(), ((MutableValueNode<Integer>) source).getValue());
                        }
                });
                scribble.getTextNode().addObserver(new Observer()
                {
                        public void onNotify(Object source)
                        {
                                setText(((MutableValueNode<String>) source).getValue());
                        }
                });
        }

        private void loadFont()
        {
                Font font = null;
                try
                {
                      InputStream inputStream = getClass().getResourceAsStream(FONT_NAME);
                      font = Font.createFont(Font.TRUETYPE_FONT, inputStream).deriveFont(ViewConstants.SCRIBBLE_FONT_SIZE);
                      setFont(font);
                      inputStream.close();
                }
                catch (Exception e) {}
        }

        private void handleTextChanged()
        {
                setBounds(getX(), getY(), (int) getPreferredSize().getWidth(),
                                          (int) getPreferredSize().getHeight());
        }

        private void setSelected(boolean selected)
        {
                if(selected)
                        alpha = ViewConstants.SCRIBBLE_SELECTED_ALPHA;
                else
                        alpha = ViewConstants.SCRIBBLE_DEFAULT_ALPHA;
                repaint();
        }

        @Override
        public void setBounds(int x, int y, int width, int height)
        {
                super.setBounds(x, y, Math.max(MIN_WIDTH, width), Math.max(MIN_HEIGHT, height));
                if(x != scribble.getX())
                        scribble.setX(x);
                if(y != scribble.getY())
                        scribble.setY(y);
                repaint();
        }

        @Override
        public void setBounds(Rectangle r)
        {
                setBounds(r.x, r.y, r.width, r.height);
        }

        @Override
        public void setLocation(int x, int y)
        {
                super.setLocation(x, y);
                if(x != scribble.getX())
                        scribble.setX(x);
                if(y != scribble.getY())
                        scribble.setY(y);
        }

        @Override
        public void setLocation(Point p)
        {
                setLocation(p.x, p.y);
        }

        @Override
        public void setText(String t)
        {
                super.setText(t);
                if(t.compareTo(scribble.getText()) != 0)
                        scribble.setText(t);
                handleTextChanged();
        }

        @Override
        protected void paintComponent(Graphics g)
        {
                //Relative to this component (x and y)
                int x = 0;
                int y = 0;
                int width = getWidth() - (PRE_X + POST_X);
                int height = getHeight() - (PRE_Y + POST_Y);

                //Draw Post-It frame on a buffered image
                BufferedImage bufferedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
                Graphics2D imageGraphics = bufferedImage.createGraphics();

                //Draw background
                imageGraphics.setColor(BACKGROUND_COLOR);
                imageGraphics.fillRect(x + PRE_X, y + PRE_Y, width, height);

                //Draw borders
                imageGraphics.setColor(BORDER_COLOR);
                imageGraphics.drawRect(x + PRE_X, y + PRE_Y, width, height);

                //Draw corners
                imageGraphics.drawImage(TOP_RIGHT_IMAGE, x + PRE_X + POST_X + width - TOP_RIGHT_IMAGE.getWidth(null), y, null);
                imageGraphics.drawImage(BOTTOM_LEFT_IMAGE, x, y + PRE_Y + POST_Y + height - BOTTOM_LEFT_IMAGE.getHeight(null), null);
                imageGraphics.drawImage(BOTTOM_RIGHT_IMAGE, x + PRE_X + POST_X + width - BOTTOM_RIGHT_IMAGE.getWidth(null),
                                              y + PRE_Y + POST_Y + 1 + height - BOTTOM_RIGHT_IMAGE.getHeight(null), null);

                //Draw close button
                if(mouseEntered && ! hasFocus())
                        imageGraphics.drawImage(CLOSE_IMAGE, x + PRE_X + CLOSE_BOTTON_TOP,
                                        y + PRE_Y + CLOSE_BOTTON_LEFT, null);

                //Draw buffered image on the textfield
                Graphics2D g2 = (Graphics2D) g;
                //When the component is dragged, the alpha channel is adjusted to
                //highlight the textfield
                g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
                g2.drawImage(bufferedImage, null, x, y);

                //Draw the textfield
                super.paintComponent(g);
        }

        public void insertUpdate(DocumentEvent e)
        {
                handleTextChanged();
        }

        public void removeUpdate(DocumentEvent e)
        {
                handleTextChanged();
        }

        public void changedUpdate(DocumentEvent e)
        {
                handleTextChanged();
        }

        public void mouseClicked(MouseEvent e)
        {
                Notation.getInstance().clearSelectedElements();
                requestFocus();
                setEditable(!isEditable());
        }

        public boolean isClosed(Rectangle2D rect)
        {
                Rectangle2D closeRect = new Rectangle2D.Double(PRE_X + CLOSE_BOTTON_TOP,
                                                               PRE_Y + CLOSE_BOTTON_LEFT,
                                                               CLOSE_IMAGE.getWidth(this),
                                                               CLOSE_IMAGE.getHeight(this));
                return rect.intersects(closeRect);
        }

        public void mousePressed(MouseEvent e) {}

        public void mouseReleased(MouseEvent e) {}

        public void mouseEntered(MouseEvent e)
        {
                mouseEntered = true;
                repaint();
        }

        public void mouseExited(MouseEvent e)
        {
                mouseEntered = false;
                repaint();
        }

        //Java bug 6223733 workaround
        @Override
        public void setEditable(boolean b)
        {
                super.setEditable(b);
                SwingUtilities.invokeLater( new Runnable()
                {
                        public void run()
                        {
                                if(isEditable())
                                        setHighlighter(highlighter);
                                else
                                        setHighlighter(null);
                                if(getCaret() != null)
                                        getCaret().setVisible(isEditable());
                        }
                });
        }

        public void focusGained(FocusEvent e)
        {
                setSelected(true);
        }

        public void focusLost(FocusEvent e)
        {
                setSelected(false);
                setEditable(false);
                if(getText().compareTo(scribble.getText()) != 0)
                        scribble.setText(getText());
        }
}
